﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChessEngine.Engine
{
    /// <summary>
    /// Default strategy. Implements Minimax and Alpha-Beta, as well as uses a stock
    /// evaluation function
    /// </summary>
    internal class NegamaxStrategy : Strategy
    {
        protected const int DEPTH_LIMIT = 5;
        protected const int MIN_SCORE = -16384;
        protected const int MAX_SCORE = 16384;
        protected static readonly int[] EVAL_PIECE_SCORE =
            new int[] { 200, 9, 5, 3, 3, 1 };

        private TranspositionTable _table = new TranspositionTable();
        private EvalStats _white = new EvalStats(), _black = new EvalStats();

        internal override Move MakeNextMove(Engine engine)
        {
            return Negamax(engine.ChessBoard);
        }

        private List<EvalMove> GenerateMoves(Board b)
        {
            List<EvalMove> moves = new List<EvalMove>(35);

            for (byte i = 0; i < (byte)b.Squares.Length; ++i)
            {
                Square square = b.Squares[i];

                // Only work with squares occupied by a piece, and are occupied by the
                // current player's piece
                if (square.Piece == null || square.Piece.PieceColor != b.WhoseMove)
                    continue;

                bool forwardDec = square.Piece.PieceColor == ChessPieceColor.White;

                // The first 3 bits represent the column
                byte sourceCol, sourceRow;
                PackedToRC(i, out sourceCol, out sourceRow);

                foreach (byte move in square.Piece.ValidMoves)
                {
                    Board cpy = b.FastCopy();

                    // For now, we will just always promote a pawn to a queen where applicable
                    Board.MovePiece(cpy, i, move, ChessPieceType.Queen);

                    PieceValidMoves.GenerateValidMoves(cpy);

                    // Make sure this move didn't put the king in check
                    if (cpy.BlackCheck && b.WhoseMove == ChessPieceColor.Black)
                    {
                        continue;
                    }
                    else if (cpy.WhiteCheck && b.WhoseMove == ChessPieceColor.White)
                    {
                        continue;
                    }


                    

                    byte destCol, destRow;
                    PackedToRC(move, out destCol, out destRow);

                    EvalMove mv;
                    moves.Add(mv = new EvalMove(cpy, sourceCol, sourceRow, destCol, destRow)
                        {
                            IsForward = forwardDec ? destRow < sourceRow : sourceRow < destRow,
                            IsRetreat = forwardDec ? sourceRow < destRow : destRow < sourceRow,
                            IsCapture = b.Squares[move].Piece != null, // It is a capture if there was a piece in this square in the source board
                            IsThreat = square.Piece.AttackedValue > 0
                        });
                }
            }

            // Order the moves from heuristic best to worst
            moves.Sort();

            return moves;
        }

        protected static void PackedToRC(byte packed, out byte column, out byte row)
        {
            // The first 3 bits are the column since it represents digits 0-7,
            // and the upper bits help us get the row once we shift by 3
            column = (byte)(packed & 0x07);
            row = (byte)((packed & ~0x07) >> 3);
        }

        private Move Negamax(Board b)
        {
            // The base move shouldn't be terminal or cutoff since a terminal would mean
            // that we are trying to find a move past endgame
            EvalMove bestMove = null;
            int bestValue = int.MinValue;

            int color = (int)b.WhoseMove;

            foreach (EvalMove child in GenerateMoves(b))
            {
                int val = -Negamax_Recurse(child.Board, DEPTH_LIMIT - 1, int.MinValue, int.MaxValue, -color);

                if (val > bestValue)
                {
                    bestMove = child;
                    bestValue = val;
                }
            }

            return bestMove.Move;
        }
        private int Negamax_Recurse(Board b, int depth, int alpha, int beta, int color)
        {
            int alphaOrig = alpha;

            // Transposition Table Lookup
            //var ttEntry = _table.Lookup(b.ZobristHash);
            //if (ttEntry != null && ttEntry.Depth >= depth)
            //{
            //    switch (ttEntry.NodeType)
            //    {
            //        case TransposeNodeType.Exact:
            //            return ttEntry.Score;
            //        case TransposeNodeType.LowerBound:
            //            if (ttEntry.Score > alpha)
            //                alpha = ttEntry.Score;
            //            break;
            //        case TransposeNodeType.UpperBound:
            //            if (ttEntry.Score < beta)
            //                beta = ttEntry.Score;
            //            break;
            //    }
            //    if (alpha >= beta)
            //        return ttEntry.Score;
            //}

            // Terminal Tests
            if (b.IsStalemate)
                return 0;
            else if (b.IsWhiteMate)
                return -color * MAX_SCORE;
            else if (b.IsBlackMate)
                return color * MAX_SCORE;
            else if (depth == 0)
                return color * Eval(b);

            int bestValue = int.MinValue;

            foreach (EvalMove child in GenerateMoves(b))
            {
                int val = -Negamax_Recurse(child.Board, depth - 1, -beta, -alpha, -color);

                if (val > bestValue)
                    bestValue = val;
                if (val > alpha)
                    alpha = val;
                if (alpha >= beta)
                    break;
            }

            // Transposition Table Store
            //if (ttEntry == null)
            //    ttEntry = new TranspositionContent() { Hash = b.ZobristHash };
            //ttEntry.Score = bestValue;
            //if (bestValue <= alphaOrig)
            //    ttEntry.NodeType = TransposeNodeType.UpperBound;
            //else if (bestValue >= beta)
            //    ttEntry.NodeType = TransposeNodeType.LowerBound;
            //else
            //    ttEntry.NodeType = TransposeNodeType.Exact;
            //ttEntry.Depth = depth;
            //_table.Store(ttEntry);

            return bestValue;
        }

        internal virtual short Eval(Board board)
        {
            // Using basic eval function from website 
            // http://chessprogramming.wikispaces.com/Evaluation
            _white.Reset();
            _black.Reset();

            foreach (Square sq in board.Squares)
            {
                if (sq.Piece == null)
                    continue;

                EvalStats curr = sq.Piece.PieceColor == ChessPieceColor.Black ?
                    _black : _white;

                ++curr.PieceCounts[(int)sq.Piece.PieceType];
                curr.Mobility += sq.Piece.ValidMoves.Count;

                if (sq.Piece.PieceType == ChessPieceType.Pawn)
                {
                    if (sq.Piece.ValidMoves.Count == 0)
                        ++curr.BlockedPawns;
                }
            }

            // The eval function always operates as white being the scored player.
            // Negamax deals with black being the evaluated player automatically

            float ret = 0;

            for (int i = 0; i < 6; ++i)
                ret += EVAL_PIECE_SCORE[i] * (_white.PieceCounts[i] - _black.PieceCounts[i]);
            ret += -0.5f * (_white.DoubledPawns - _black.DoubledPawns +
                                _white.BlockedPawns - _black.BlockedPawns +
                                _white.IsolatedPawns - _black.IsolatedPawns);
            ret += 0.1f * (_white.Mobility - _black.Mobility);

            return (short)(ret * 100.0f);
        }

        protected class EvalMove : IComparable<EvalMove>
        {
            public bool IsCapture;
            public bool IsThreat;
            public bool IsForward;
            public bool IsRetreat;
            public Move Move;
            public Board Board;

            public EvalMove(Board board, byte sourceCol, byte sourceRow, byte destCol, byte destRow)
            {
                Move = new Move(sourceCol, sourceRow, destCol, destRow);
                Board = board;
            }
            public EvalMove(Board board, byte srcPosition, byte destPosition)
            {
                byte sourceCol, sourceRow, destCol, destRow;
                PackedToRC(srcPosition, out sourceCol, out sourceRow);
                PackedToRC(destPosition, out destCol, out destRow);

                Move = new Move(sourceCol, sourceRow, destCol, destRow);
                Board = board;
            }

            public int CompareTo(EvalMove other)
            {
                int cmp = -IsCapture.CompareTo(other.IsCapture);

                if (cmp != 0)
                    return cmp;

                cmp = -IsThreat.CompareTo(other.IsThreat);

                if (cmp != 0)
                    return cmp;

                cmp = -IsForward.CompareTo(other.IsForward);

                if (cmp != 0)
                    return cmp;

                return -IsRetreat.CompareTo(other.IsRetreat);
            }
        }
        protected class EvalStats
        {
            public byte[] PieceCounts = new byte[EVAL_PIECE_SCORE.Length];
            public int Mobility = 0;
            public int DoubledPawns = 0;
            public int BlockedPawns = 0;
            public int IsolatedPawns = 0;

            public void Reset()
            {
                for (int i = 0; i < PieceCounts.Length; ++i)
                    PieceCounts[i] = 0;
                Mobility = 0;
                DoubledPawns = 0;
                BlockedPawns = 0;
                IsolatedPawns = 0;
            }
        }
        
    }
}
